#include "wx/wxprec.h"
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include "wx/thread.h"
#include "wx/file.h"
#include "wx/sound.h"
#include "wx/dynlib.h"

static wxMutex gs_soundMutex;

void wxSoundData::IncRef() {
  wxMutexLocker locker( gs_soundMutex );
  m_refCnt++;
}

void wxSoundData::DecRef() {
  wxMutexLocker locker( gs_soundMutex );
  if( --m_refCnt == 0 ) {
    delete this;
  }
}

wxSoundData::~wxSoundData() {
  delete[] m_dataWithHeader;
}

class wxSoundBackendNull : public wxSoundBackend {
  public:
    wxString GetName() const { return _( "No sound" ); }
    int GetPriority() const { return 0; }
    bool IsAvailable() const { return true; }
    bool HasNativeAsyncPlayback() const { return true; }
    bool Play( wxSoundData * ( data ), unsigned( flags ),
               volatile wxSoundPlaybackStatus * ( status ) ) { return true; }
    void Stop() {}
    bool IsPlaying() const { return false; }
};

#define AUDIODEV   "/dev/dsp"    // 音频设备的默认路径

class wxSoundBackendOSS : public wxSoundBackend {
  public:
    wxString GetName() const { return _T( "Open Sound System" ); }
    int GetPriority() const { return 10; }
    bool IsAvailable() const;
    bool HasNativeAsyncPlayback() const { return false; }
    bool Play( wxSoundData *data, unsigned flags, volatile wxSoundPlaybackStatus *status );
    void Stop() {}
    bool IsPlaying() const { return false; }
  private:
    int OpenDSP( const wxSoundData *data );
    bool InitDSP( int dev, const wxSoundData *data );
    int m_DSPblkSize;        // Size of the DSP buffer
    bool m_needConversion;
};

bool wxSoundBackendOSS::IsAvailable() const {
  int fd;
  fd = open( AUDIODEV, O_WRONLY | O_NONBLOCK );
  if( fd < 0 ) {
    return false;
  }
  close( fd );
  return true;
}

bool wxSoundBackendOSS::Play( wxSoundData *data, unsigned flags, volatile wxSoundPlaybackStatus *status ) {
  int dev = OpenDSP( data );
  if( dev < 0 ) {
    return false;
  }
  ioctl( dev, SNDCTL_DSP_SYNC, 0 );
  do {
    bool play = true;
    int i;
    unsigned l = 0;
    size_t datasize = data->m_dataBytes;
    do {
      if( status->m_stopRequested ) {
        wxLogTrace( _T( "sound" ), _T( "playback stopped" ) );
        close( dev );
        return true;
      }
      i = ( int )( ( l + m_DSPblkSize ) < datasize ? m_DSPblkSize : ( datasize - l ) );
      if( write( dev, &data->m_data[l], i ) != i ) {
        play = false;
      }
      l += i;
    } while( play && l < datasize );
  } while( flags & wxSOUND_LOOP );
  close( dev );
  return true;
}

int wxSoundBackendOSS::OpenDSP( const wxSoundData *data ) {
  int dev = -1;
  if( ( dev = open( AUDIODEV, O_WRONLY, 0 ) ) < 0 ) {
    return -1;
  }
  if( !InitDSP( dev, data ) || m_needConversion ) {
    close( dev );
    return -1;
  }
  return dev;
}


bool wxSoundBackendOSS::InitDSP( int dev, const wxSoundData *data ) {
  unsigned tmp;
  if( ioctl( dev, SNDCTL_DSP_RESET, 0 ) < 0 ) {
    wxLogTrace( _T( "sound" ), _T( "unable to reset dsp" ) );
    return false;
  }
  m_needConversion = false;
  tmp = data->m_bitsPerSample;
  if( ioctl( dev, SNDCTL_DSP_SAMPLESIZE, &tmp ) < 0 ) {
    wxLogTrace( _T( "sound" ), _T( "IOCTL failure (SNDCTL_DSP_SAMPLESIZE)" ) );
    return false;
  }
  if( tmp != data->m_bitsPerSample ) {
    wxLogTrace( _T( "sound" ), _T( "Unable to set DSP sample size to %d (wants %d)" ), data->m_bitsPerSample, tmp );
    m_needConversion = true;
  }
  unsigned stereo = data->m_channels == 1 ? 0 : 1;
  tmp = stereo;
  if( ioctl( dev, SNDCTL_DSP_STEREO, &tmp ) < 0 ) {
    wxLogTrace( _T( "sound" ), _T( "IOCTL failure (SNDCTL_DSP_STEREO)" ) );
    return false;
  }
  if( tmp != stereo ) {
    wxLogTrace( _T( "sound" ), _T( "Unable to set DSP to %s." ), stereo ?  _T( "stereo" ) : _T( "mono" ) );
    m_needConversion = true;
  }
  tmp = data->m_samplingRate;
  if( ioctl( dev, SNDCTL_DSP_SPEED, &tmp ) < 0 ) {
    wxLogTrace( _T( "sound" ), _T( "IOCTL failure (SNDCTL_DSP_SPEED)" ) );
    return false;
  }
  if( tmp != data->m_samplingRate ) {
    if( data->m_samplingRate - tmp > ( tmp * .01 ) || tmp - data->m_samplingRate > ( tmp * .01 ) ) {
      wxLogTrace( _T( "sound" ), _T( "Unable to set DSP sampling rate to %d (wants %d)" ), data->m_samplingRate, tmp );
      m_needConversion = true;
    }
  }
  if( ioctl( dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize ) < 0 ) {
    wxLogTrace( _T( "sound" ), _T( "IOCTL failure (SNDCTL_DSP_GETBLKSIZE)" ) );
    return false;
  }
  return true;
}

class wxSoundSyncOnlyAdaptor;

class wxSoundAsyncPlaybackThread : public wxThread {
  public:
    wxSoundAsyncPlaybackThread( wxSoundSyncOnlyAdaptor *adaptor, wxSoundData *data, unsigned flags )
      : wxThread(), m_adapt( adaptor ), m_data( data ), m_flags( flags ) {}
    virtual ExitCode Entry();

  protected:
    wxSoundSyncOnlyAdaptor *m_adapt;
    wxSoundData *m_data;
    unsigned m_flags;
};

class wxSoundSyncOnlyAdaptor : public wxSoundBackend {
  public:
    wxSoundSyncOnlyAdaptor( wxSoundBackend *backend )
      : m_backend( backend ), m_playing( false ) {}
    virtual ~wxSoundSyncOnlyAdaptor() {
      delete m_backend;
    }
    wxString GetName() const {
      return m_backend->GetName();
    }
    int GetPriority() const {
      return m_backend->GetPriority();
    }
    bool IsAvailable() const {
      return m_backend->IsAvailable();
    }
    bool HasNativeAsyncPlayback() const {
      return true;
    }
    bool Play( wxSoundData *data, unsigned flags,
               volatile wxSoundPlaybackStatus *status );
    void Stop();
    bool IsPlaying() const;

  private:
    friend class wxSoundAsyncPlaybackThread;

    wxSoundBackend *m_backend;
    bool m_playing;
    wxMutex m_mutexRightToPlay;
    wxSoundPlaybackStatus m_status;
};

wxThread::ExitCode wxSoundAsyncPlaybackThread::Entry() {
  m_adapt->m_backend->Play( m_data, m_flags & ~wxSOUND_ASYNC, &m_adapt->m_status );
  m_data->DecRef();
  m_adapt->m_playing = false;
  m_adapt->m_mutexRightToPlay.Unlock();
  wxLogTrace( _T( "sound" ), _T( "terminated async playback thread" ) );
  return 0;
}

bool wxSoundSyncOnlyAdaptor::Play( wxSoundData *data, unsigned flags, volatile wxSoundPlaybackStatus *status ) {
  Stop();
  if( flags & wxSOUND_ASYNC ) {
    m_mutexRightToPlay.Lock();
    m_status.m_playing = true;
    m_status.m_stopRequested = false;
    data->IncRef();
    wxThread *th = new wxSoundAsyncPlaybackThread( this, data, flags );
    th->Create();
    th->Run();
    wxLogTrace( _T( "sound" ), _T( "launched async playback thread" ) );
    return true;
  } else {
    m_mutexRightToPlay.Lock();
    bool rv = m_backend->Play( data, flags, status );
    m_mutexRightToPlay.Unlock();
    return rv;
  }
}

void wxSoundSyncOnlyAdaptor::Stop() {
  wxLogTrace( _T( "sound" ), _T( "asking audio to stop" ) );
  m_status.m_stopRequested = true;
  m_mutexRightToPlay.Lock();
  m_mutexRightToPlay.Unlock();
  wxLogTrace( _T( "sound" ), _T( "audio was stopped" ) );
}

bool wxSoundSyncOnlyAdaptor::IsPlaying() const {
  return m_status.m_playing;
}

wxSoundBackend *wxSound::ms_backend = NULL;

wxSound::wxSound() : m_data( NULL ) {
}

wxSound::wxSound( const wxString& sFileName, bool isResource ) : m_data( NULL ) {
  Create( sFileName, isResource );
}

wxSound::wxSound( int size, const wxByte* data ) : m_data( NULL ) {
  Create( size, data );
}

wxSound::~wxSound() {
  Free();
}

bool wxSound::Create( const wxString& fileName, bool isResource ) {
  wxASSERT_MSG( !isResource, _T( "Loading sound from resources is only supported on Windows" ) );
  Free();
  wxFile fileWave;
  if( !fileWave.Open( fileName, wxFile::read ) ) {
    return false;
  }
  wxFileOffset lenOrig = fileWave.Length();
  if( lenOrig == wxInvalidOffset ) {
    return false;
  }
  size_t len = wx_truncate_cast( size_t, lenOrig );
  wxUint8 *data = new wxUint8[len];
  if( fileWave.Read( data, len ) != lenOrig ) {
    delete [] data;
    wxLogError( _( "Couldn't load sound data from '%s'." ), fileName.c_str() );
    return false;
  }
  if( !LoadWAV( data, len, false ) ) {
    delete [] data;
    wxLogError( _( "Sound file '%s' is in unsupported format." ),
                fileName.c_str() );
    return false;
  }
  return true;
}

bool wxSound::Create( int size, const wxByte* data ) {
  wxASSERT( data != NULL );
  Free();
  if( !LoadWAV( data, size, true ) ) {
    wxLogError( _( "Sound data are in unsupported format." ) );
    return false;
  }
  return true;
}

void wxSound::EnsureBackend() {
  if( !ms_backend ) {
    if( !ms_backend ) {
      ms_backend = new wxSoundBackendOSS();
      if( !ms_backend->IsAvailable() ) {
        wxDELETE( ms_backend );
      }
    }
    if( !ms_backend ) {
      ms_backend = new wxSoundBackendNull();
    }
    if( !ms_backend->HasNativeAsyncPlayback() ) {
      ms_backend = new wxSoundSyncOnlyAdaptor( ms_backend );
    }
    wxLogTrace( _T( "sound" ), _T( "using backend '%s'" ), ms_backend->GetName().c_str() );
  }
}

void wxSound::UnloadBackend() {
  if( ms_backend ) {
    wxLogTrace( _T( "sound" ), _T( "unloading backend" ) );
    Stop();
    delete ms_backend;
    ms_backend = NULL;
  }
}

bool wxSound::DoPlay( unsigned flags ) const {
  wxCHECK_MSG( IsOk(), false, _T( "Attempt to play invalid wave data" ) );
  EnsureBackend();
  wxSoundPlaybackStatus status;
  status.m_playing = true;
  status.m_stopRequested = false;
  return ms_backend->Play( m_data, flags, &status );
}

void wxSound::Stop() {
  if( ms_backend ) {
    ms_backend->Stop();
  }
}

bool wxSound::IsPlaying() {
  if( ms_backend ) {
    return ms_backend->IsPlaying();
  } else {
    return false;
  }
}

void wxSound::Free() {
  if( m_data ) {
    m_data->DecRef();
  }
}

typedef struct {
  wxUint32 uiSize;
  wxUint16 uiFormatTag;
  wxUint16 uiChannels;
  wxUint32 ulSamplesPerSec;
  wxUint32 ulAvgBytesPerSec;
  wxUint16 uiBlockAlign;
  wxUint16 uiBitsPerSample;
} WAVEFORMAT;

#define WAVE_FORMAT_PCM  1
#define WAVE_INDEX       8
#define FMT_INDEX       12

bool wxSound::LoadWAV( const wxUint8 *data, size_t length, bool copyData ) {
  if( length < 44 ) {
    return false;
  }
  WAVEFORMAT waveformat;
  memcpy( &waveformat, &data[FMT_INDEX + 4], sizeof( WAVEFORMAT ) );
  waveformat.uiSize = wxUINT32_SWAP_ON_BE( waveformat.uiSize );
  waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE( waveformat.uiFormatTag );  // 20 格式标记
  waveformat.uiChannels = wxUINT16_SWAP_ON_BE( waveformat.uiChannels );  // 22 通道数
  waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE( waveformat.ulSamplesPerSec );  // 28 采样率
  waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE( waveformat.ulAvgBytesPerSec );  // 32 每秒的采样字节数
  waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE( waveformat.uiBlockAlign );  // 每次采样的字节数
  waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE( waveformat.uiBitsPerSample );  //采样位数
  wxUint32 ul;
  memcpy( &ul, &data[FMT_INDEX + waveformat.uiSize + 12], 4 );
  ul = wxUINT32_SWAP_ON_BE( ul );
  if( length < ul + FMT_INDEX + waveformat.uiSize + 16 ) {
    return false;
  }
  if( memcmp( data, "RIFF", 4 ) != 0 ) {
    return false;
  }
  if( memcmp( &data[WAVE_INDEX], "WAVE", 4 ) != 0 ) {
    return false;
  }
  if( memcmp( &data[FMT_INDEX], "fmt ", 4 ) != 0 ) {
    return false;
  }
  if( memcmp( &data[FMT_INDEX + waveformat.uiSize + 8], "data", 4 ) != 0 ) {
    return false;
  }
  if( waveformat.uiFormatTag != WAVE_FORMAT_PCM ) {
    return false;
  }
  if( waveformat.ulSamplesPerSec !=waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign ) {
    return false;
  }
  m_data = new wxSoundData;
  m_data->m_channels = waveformat.uiChannels;
  m_data->m_samplingRate = waveformat.ulSamplesPerSec;
  m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
  m_data->m_samples = ul / ( m_data->m_channels * m_data->m_bitsPerSample / 8 );
  m_data->m_dataBytes = ul;
  if( copyData ) {
    m_data->m_dataWithHeader = new wxUint8[length];
    memcpy( m_data->m_dataWithHeader, data, length );
  } else {
    m_data->m_dataWithHeader = ( wxUint8* )data;
  }
  m_data->m_data = ( &m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8] );
  return true;
}

class wxSoundCleanupModule: public wxModule {
  public:
    bool OnInit() { return true; }
    void OnExit() { wxSound::UnloadBackend(); }
    DECLARE_DYNAMIC_CLASS( wxSoundCleanupModule )
};

IMPLEMENT_DYNAMIC_CLASS( wxSoundCleanupModule, wxModule )
